import Link from 'next/link'; import { notFound } from 'next/navigation'; import { requireAdmin } from '@/lib/admin/auth'; import { connectorDetail } from '@/lib/admin/queries'; import { connectorAction, updateConnectorConfig } from '@/lib/admin/actions'; import { AdminShell, ActionButton, StatusPill, Kpi, fmtTs, n } from '@/components/admin/shell'; import { HealthLabel, SourceLogo, Progress, Chip, Json } from '@/components/admin/connector-bits'; import { Table, th, td, tdNum } from '@/components/ui/primitives'; export default async function ConnectorInspectPage({ params }: { params: Promise<{ id: string }> }) { await requireAdmin(); const { id } = await params; const d = await connectorDetail(id); if (!d) notFound(); const c = d.connector; const health = (d.health?.health ?? {}) as Record; const outputs = (d.outputs ?? {}) as Record; const meta = (c.meta ?? {}) as Record; const activeBackfill = d.backfills.find((b) => b.status === 'running' || b.status === 'paused'); const fieldStats = d.fieldStats as Array<{ field: string; day: string; total: number; nulls: number }>; const today = new Date().toISOString().slice(0, 10); const nullRates = Object.values( fieldStats.reduce>((acc, r) => { const e = (acc[r.field] ??= { field: r.field, today: [0, 0], base: [0, 0] }); const t = r.day === today ? e.today : e.base; t[0] += Number(r.total); t[1] += Number(r.nulls); return acc; }, {}), ); const actions = [ ['probe', 'Test', 'neutral'], ['run', 'Run now', 'neutral'], ['backfill', activeBackfill ? 'Continue backfill' : 'Backfill', 'neutral'], ...(activeBackfill?.status === 'running' ? [['backfill_pause', 'Pause backfill', 'neutral']] : []), ...(activeBackfill ? [['backfill_reset', 'Reset backfill', 'danger']] : []), ['retry', 'Retry from cursor', 'neutral'], c.status === 'active' ? ['pause', 'Pause', 'danger'] : ['resume', 'Resume', 'primary'], c.status === 'maintenance' ? ['resume', 'End maintenance', 'primary'] : ['maintenance', 'Maintenance', 'neutral'], ] as const; return ( {String(c.id)} · source {String(c.source_id)} · {String(meta.domain ?? '')} · {String(meta.country ?? (c.regions as string[])?.[0] ?? 'global')} · {(c.engine_priority as string[]).join(' → ')} · {String(meta.acquisitionMethod ?? '')} · categories {(c.categories as string[]).join(', ')} · back } actions={
{actions.map(([a, label, tone]) => (
))}
} >
{((meta.capabilities as string[]) ?? []).map((cap) => ( {cap} ))} {((meta.requires as string[]) ?? []).map((r) => ( requires {r} ))} refresh {String(meta.refreshClass ?? '')} · {n(c.refresh_frequency_minutes)} min history {String(meta.historicalDepth ?? 'none')} {meta.termsUrl ? ( terms ) : null}
} sub={<>health } /> 0 ? 'alert' : undefined} /> ) ?? {}).map(([k, v]) => `${k}×${v}`).join(' ') || '0'} sub={`${n(health.circuit_refusals_24h)} refused by circuit`} tone={Object.keys((health.http_errors as Record) ?? {}).length ? 'loss' : undefined} /> a.startsWith('result_count_collapse')) ? 'alert' : undefined} />
{activeBackfill || d.backfills.length ? (

Historical backfill campaigns

{d.backfills.map((b) => ( ))}
StartedStatusProgressPagesItemsReached dateRunsErrorsLast cursorLast error
{fmtTs(b.started_at)} {n(b.pages_processed)}{b.total_pages ? ` / ${n(b.total_pages)}` : ''} {n(b.items_processed)} {b.reached_date ? String(b.reached_date) : '—'} {n(b.runs)} {n(b.errors)} {JSON.stringify(b.last_cursor)} {String(b.last_error ?? '')}
) : null} {nullRates.length ? (

Field presence (schema-drift monitor)

{nullRates.map((f) => { const tRate = f.today[0] ? f.today[1] / f.today[0] : null; const bRate = f.base[0] ? f.base[1] / f.base[0] : null; const drift = tRate !== null && bRate !== null && tRate - bRate >= 0.3; return (
{f.field} null today {tRate === null ? '—' : `${Math.round(tRate * 100)}%`} · 7d {bRate === null ? '—' : `${Math.round(bRate * 100)}%`}
); })}
) : null} {d.anomalies.length ? (

Anomalies (recent runs)

    {d.anomalies.map((a, i) =>
  • {a}
  • )}
) : null}

Runs

{d.runs.length === 0 ? : null} {d.runs.map((r) => ( ))}
StartedStatusTriggerPagesRawNorm.Dup.Rej.CreditsEnginesError
No runs yet.
{fmtTs(r.started_at)}
{r.finished_at ? `${Math.round((new Date(String(r.finished_at)).getTime() - new Date(String(r.started_at)).getTime()) / 1000)} s` : 'running'}
{String(r.trigger)} {n(r.pages_success)}/{n(r.pages_attempted)} {n(r.records_raw)} {n(r.records_normalized)} {n(r.records_duplicate)} {n(r.records_rejected)} {n(r.cost_credits, 1)} {Object.entries((r.engine_stats ?? {}) as Record).map(([k, v]) => `${k} ${v.success}/${v.attempts}`).join(' · ')} {r.error ? String(r.error) : ''}

Raw records (latest)

    {d.rawSample.length === 0 ?
  • None yet.
  • : null} {d.rawSample.map((r) => (
  • {String(r.kind)} · {String(r.engine)} · {String(r.http_status ?? '')}{fmtTs(r.fetched_at)}
    {String(r.url)}
    {r.process_error ?

    {String(r.process_error)}

    :

    {r.processed_at ? `processed ${fmtTs(r.processed_at)}` : 'unprocessed'}

    } view payload
  • ))}

Normalized records (latest)

{d.normalized.map((x) => `${x.status} ${n(x.n)}`).join(' · ')}
    {d.normalizedSample.length === 0 ?
  • None yet.
  • : null} {d.normalizedSample.map((r) => (
  • {String(r.raw_title ?? '')}

    {String(r.kind)} · {r.price ? `${String(r.price)} ${String(r.currency ?? '')}` : ''} · {r.match_method ? `${String(r.match_method)} ${r.match_confidence ? Math.round(Number(r.match_confidence) * 100) + '%' : ''}` : ''} {r.reject_reason ? `· ${String(r.reject_reason)}` : ''}

    view record
  • ))}

Costs (30d)

{d.costs.length === 0 ? : null} {d.costs.map((r, i) => )}
DayKindCreditsUSD est.
No cost events.
{String(r.day)}{String(r.kind)}{n(r.credits, 1)}${n(r.usd, 3)}

Mapping & schedule